package com.zoe.onelink.core.validation.annotation;


import cn.hutool.core.collection.CollectionUtil;
import com.zoe.onelink.core.validation.strategy.AbstractValidateStrategy;
import com.zoe.onelink.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;


/**
 * 策略校验注解（支持数据库等非静态数据的校验）
 */
@Target({FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = StrategyValid.StrategyValidator.class)
@Documented
public @interface StrategyValid {
    /**
     * 验证失败时使用的默认错误消息。
     */
    String message() default "未定义策略枚举";

    /**
     * 指定在验证过程中要应用的组。
     * 这个属性通常在组合验证时使用。
     */
    Class<?>[] groups() default {};

    /**
     * 指定用于指定验证失败时的负载类型。
     * 这个属性通常用于向客户端提供特定类型的错误消息。
     */
    Class<? extends Payload>[] payload() default {};

    /**
     * 策略类
     */
    Class<? extends AbstractValidateStrategy>[] strategyClass();

    /**
     * 验证注解参数的验证器类，用于验证指定的验证策略是否通过。
     * 这个验证器使用 AbstractValidateStrategy 类的子类来验证参数，每个子类代表了一种验证策略。
     */
    class StrategyValidator implements ConstraintValidator<StrategyValid, Object> {

        private static final Logger LOGGER = LoggerFactory.getLogger(StrategyValidator.class);

        /**
         * 验证策略的 Class 对象数组。
         * 这些 Class 对象必须是 AbstractValidateStrategy 类的子类。
         */
        private Class<? extends AbstractValidateStrategy>[] strategyClass;

        /**
         * 验证失败时使用的错误消息。
         */
        private String message;

        /**
         * 用于存储验证策略的映射表。
         * 这个映射表将验证策略的名称映射到实际的验证策略实例。
         */
        private Map<String, AbstractValidateStrategy> validateStrategyMap = new HashMap<>();

        /**
         * 创建一个新的 StrategyValidator 实例。
         *
         * @param validateStrategyList 包含验证策略的列表。
         *                             这些验证策略必须是 AbstractValidateStrategy 类的子类。
         */
        public StrategyValidator(List<AbstractValidateStrategy> validateStrategyList) {
            if (CollectionUtil.isNotEmpty(validateStrategyList)) {
                validateStrategyList.stream().forEach(item -> this.validateStrategyMap.put(item.name(), item));
            }
        }

        @Override
        public void initialize(StrategyValid constraintAnnotation) {
            this.strategyClass = constraintAnnotation.strategyClass();
            this.message = constraintAnnotation.message();
        }

        @Override
        public boolean isValid(Object value, ConstraintValidatorContext context) {

            Map<String, Object> contextMap = new HashMap<>();
            boolean result = true;
            for (Class strategyClass : strategyClass) {
                String strategyName = strategyClass.getSimpleName();
                AbstractValidateStrategy abstractValidateStrategy = this.validateStrategyMap.get(strategyName);
                result = abstractValidateStrategy.valid(value, contextMap);
                // 单个策略可以是嵌套策略(复杂业务策略由低级策略组成),获取实际命中策略
                String hitStrategyName = contextMap.get("Strategy") != null ? (String) contextMap.get("Strategy") : strategyName;
                AbstractValidateStrategy hitValidateStrategy = this.validateStrategyMap.get(hitStrategyName);
                LOGGER.info("[{}]({}) check:{} result:[{}]", hitStrategyName, hitValidateStrategy.message(), value, result ? "success" : "failure");
                // 有错误立即结束
                if (!result) {
                    // 找出实际命中的策略
                    context.disableDefaultConstraintViolation();
                    String template = StrUtil.isNotEmpty(hitValidateStrategy.message().getMessageKey()) ? hitValidateStrategy.message().getMessageKey() : message;
                    context.buildConstraintViolationWithTemplate(template).addConstraintViolation();
                    break;
                }
            }

            return result;
        }
    }

}